package com.ld.shieldsb.qrcode.writer;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import javax.imageio.ImageIO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.decoder.Version;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;
import com.ld.shieldsb.qrcode.writer.model.Location;
import com.ld.shieldsb.qrcode.writer.model.QRCodeData;

/**
 * 彩色输出器，重写的二维码输出器
 * 
 * @ClassName ColorQRCodeWriter
 * @author <a href="mailto:donggongai@126.com" target="_blank">吕凯</a>
 * @date 2017年8月11日 上午8:32:24
 *
 */
public class QRCodeDataFillWriter extends QRCodeNoPaddingWriter {
    protected static final int QUIET_ZONE_SIZE = 4;
    protected static final Logger log = LoggerFactory.getLogger(QRCodeDataFillWriter.class);

    public Map<String, Object> encodeColor(String contents, BarcodeFormat format, int width, int height) throws WriterException {
        return encodeColor(contents, format, width, height, null);
    }

    public Map<String, Object> encodeColor(String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType, ?> hints)
            throws WriterException {
        if (contents.isEmpty()) {
            throw new IllegalArgumentException("Found empty contents");
        }

        if (format != BarcodeFormat.QR_CODE) {
            throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format);
        }

        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + height);
        }

        ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
        int quietZone = QUIET_ZONE_SIZE;
        if (hints != null) {
            if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) {
                errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString());
            }
            if (hints.containsKey(EncodeHintType.MARGIN)) {
                quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString());
            }
        }
        QRCode code = Encoder.encode(contents, errorCorrectionLevel, hints);
        return renderResult(code, width, height, quietZone);
    }

    private static Map<String, Object> renderResult(QRCode code, int width, int height, int quietZone) {
        ByteMatrix input = code.getMatrix();
        if (input == null) {
            throw new IllegalStateException();
        }
        int inputWidth = input.getWidth();
        int inputHeight = input.getHeight();
        // 这里qrWidth就是原始的二维码的宽度了，包含8单位宽度的白边
        int qrWidth = inputWidth + (quietZone * 2);
        int qrHeight = inputHeight + (quietZone * 2);
        // 依据用户的输入宽高，计算最后的输出宽高
        int outputWidth = Math.max(width, qrWidth);
        int outputHeight = Math.max(height, qrHeight);

        // 计算缩放比例
        int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
        // Padding includes both the quiet zone and the extra white pixels to accommodate the requested
        // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone.
        // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will
        // handle all the padding from 100x100 (the actual QR) up to 200x160.
        // 计算白边的宽度
        int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
        int topPadding = (outputHeight - (inputHeight * multiple)) / 2;

        BitMatrix output = new BitMatrix(outputWidth, outputHeight);

        // 嵌套循环，将ByteMatrix的内容计算padding后转换成BitMatrix
        for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
            // Write the contents of this row of the barcode
            for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
                if (input.get(inputX, inputY) == 1) {
                    output.setRegion(outputX, outputY, multiple, multiple);
                }
            }
        }
        if (quietZone == 0) {
            output = deleteWhite(output);
        }
        Map<String, Object> map = new HashMap<>();
        map.put("BitMatrix", output);
        map.put("Version", code.getVersion());
        map.put("Mode", code.getMode());
        return map;
    }

    public BufferedImage encodeBufImg(String content, int width, int height, boolean all) {
        return encodeBufImg(content, width, height, null, null, null, all);
    }

    public BufferedImage encodeBufImg(String content, int width) {
        BufferedImage bufferImg = encodeBufImg(content, width, width, true);
        return bufferImg;
    }

    /**
     * 生成基点颜色不同的图片
     *
     * @param content
     *            需要生成的二维码的内容
     * @param width
     *            二维码宽
     * @param height
     *            二维码高
     * @param topLeftColor
     *            左基点颜色
     * @param topRightColor
     *            右顶基点颜色
     * @param bottomLeftColor
     *            左底基点颜色
     * @return
     */
    public BufferedImage encodeBufImg(String content, int width, int height, Integer topLeftColor, Integer topRightColor,
            Integer bottomLeftColor, boolean all) {

        BufferedImage image = null;

        try {
            int startModel = 2;
            int endModel = 5;
            if (all) {
                startModel = 0;
                endModel = 7;
            }
//            // 定义二维码内容参数
            Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
//            // 设置字符集编码格式
            hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
            // 设置二维码排错率，可选L(7%)、M(15%)、Q(25%)、H(30%)，排错率越高可存储的信息越少，但对二维码清晰度的要求越小
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
            // 设置边框距
            hints.put(EncodeHintType.MARGIN, 0); // 0-4
//          二维码一共有40个尺寸。官方叫版本Version。Version 1是21 x 21的矩阵，Version 2是 25 x 25的矩阵，Version 3是29的尺寸，每增加一个version，就会增加4的尺寸，
//          公式是：(V-1)*4 + 21（V是版本号） 最高Version 40，(40-1)*4+21 = 177，所以最高是177 x 177 的正方形。……而三个角的探测点的长度相对来讲是固定的。
//            int qrVersion = 3;
//            hints.put(EncodeHintType.QR_VERSION, qrVersion);
//            // 生成二维码
            Map<String, Object> map = encodeColor(content, BarcodeFormat.QR_CODE, width, height, hints);
            BitMatrix matrix = (BitMatrix) map.get("BitMatrix");
            Version version = (Version) map.get("Version");
            int qrWidth = matrix.getWidth(); // 二维码区的大小
            int qrHeight = matrix.getHeight();

//            Integer[] is = ArrayUtils.toObject(matrix.getTopLeftOnBit()); // getEnclosingRectangle()返回4个值，分别代表left、top、width、height
//            System.out.println(" " + Arrays.asList(is));

            QRCodeData data = new QRCodeData(matrix, version, startModel, endModel);

            // 得到三个基准点的起始与终点
            int[] pixels = new int[qrWidth * qrHeight]; // 创建色彩元素
            for (int y = 0; y < matrix.getHeight(); y++) {
                for (int x = 0; x < matrix.getWidth(); x++) {
                    if (inLeftTop(x, y, data)) { // 左上角
                        topLeftColor = fillLeftTop(topLeftColor, matrix, qrWidth, pixels, y, x);
                    } else if (inRightTop(x, y, data)) { // 右上角
                        topRightColor = fillRightTop(topRightColor, matrix, qrWidth, pixels, y, x);
                    } else if (inLeftBottom(x, y, data)) { // 左下角
                        bottomLeftColor = fillLeftBottom(bottomLeftColor, matrix, qrWidth, pixels, y, x);
                    } else {
                        fillOthers(matrix, qrWidth, pixels, y, x);
                        // 普通
//                        pixels[y * qrWidth + x] = matrix.get(x, y) ? Color.BLACK.getRGB() : Color.WHITE.getRGB();
                    }
                }
            }
            image = toBufferedImage(matrix, width, height, pixels);
            image = extendDealImage(image, data);
        } catch (Exception e) {
            log.error("", e);
        }
        return image;
    }

    // 填充剩余区域
    public void fillOthers(BitMatrix matrix, int qrWidth, int[] pixels, int y, int x) {
        pixels[getPixelsIndex(qrWidth, y, x)] = matrix.get(x, y) ? Color.BLACK.getRGB() : Color.WHITE.getRGB();
    }

    /**
     * 计算在像素集合中的下标
     * 
     * @Title getPixelsIndex
     * @author 吕凯
     * @date 2019年10月25日 上午11:29:54
     * @param qrWidth
     * @param y
     * @param x
     * @return int
     */
    public int getPixelsIndex(int qrWidth, int y, int x) {
        return y * qrWidth + x;
    }

    // 填充左下角颜色
    public Integer fillLeftBottom(Integer bottomLeftColor, BitMatrix matrix, int qrWidth, int[] pixels, int y, int x) {
        if (bottomLeftColor == null) {
            bottomLeftColor = Color.BLACK.getRGB();
        }
        pixels[getPixelsIndex(qrWidth, y, x)] = matrix.get(x, y) ? bottomLeftColor : Color.WHITE.getRGB();
        return bottomLeftColor;
    }

    // 填充右上角颜色
    public Integer fillRightTop(Integer topRightColor, BitMatrix matrix, int qrWidth, int[] pixels, int y, int x) {
        if (topRightColor == null) {
            topRightColor = Color.BLACK.getRGB();
        }
        pixels[getPixelsIndex(qrWidth, y, x)] = matrix.get(x, y) ? topRightColor : Color.WHITE.getRGB();
        return topRightColor;
    }

    // 填充左上角颜色
    public Integer fillLeftTop(Integer topLeftColor, BitMatrix matrix, int qrWidth, int[] pixels, int y, int x) {
        if (topLeftColor == null) {
            topLeftColor = Color.BLACK.getRGB();
        }
        pixels[getPixelsIndex(qrWidth, y, x)] = matrix.get(x, y) ? topLeftColor : Color.WHITE.getRGB();
        return topLeftColor;
    }

    public boolean inLocation(int x, int y, Location leftTopArea) {
        return x >= leftTopArea.getX() && x < leftTopArea.getX() + leftTopArea.getWidth() && y >= leftTopArea.getY()
                && y < leftTopArea.getY() + leftTopArea.getHeight();
    }

    // 左上角
    public boolean inLeftTop(int x, int y, QRCodeData data) {
        return inLocation(x, y, data.getLeftTopArea());
    }

    // 右上角
    public boolean inRightTop(int x, int y, QRCodeData data) {
        return inLocation(x, y, data.getRightTopArea());
    }

    // 左下角
    public boolean inLeftBottom(int x, int y, QRCodeData data) {
        return inLocation(x, y, data.getLeftBottomArea());
    }

    /**
     * 生成图片后的扩展处理
     * 
     * @Title extendDealImage
     * @author 吕凯
     * @date 2019年10月25日 上午11:06:16
     * @param image
     * @param data
     * @return BufferedImage
     */
    public BufferedImage extendDealImage(BufferedImage image, QRCodeData data) {
        return image;
    }

    /**
     * 转化为BufferedImage，尺寸不一致时进行缩放
     * 
     * @Title toBufferedImage
     * @author 吕凯
     * @date 2019年10月25日 上午11:06:34
     * @param matrix
     * @param width
     * @param height
     * @param pixels
     * @return
     * @throws IOException
     *             BufferedImage
     */
    public BufferedImage toBufferedImage(BitMatrix matrix, int width, int height, int[] pixels) throws IOException {
        int qrCodeWidth = matrix.getWidth();
        int qrCodeHeight = matrix.getHeight();
        BufferedImage qrCode = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_RGB);
        qrCode.getRaster().setDataElements(0, 0, qrCodeWidth, qrCodeHeight, pixels); // 填充色彩

        // 若二维码的实际宽高和预期的宽高不一致, 则缩放
        if (qrCodeWidth != width || qrCodeHeight != height) {
            BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            tmp.getGraphics().drawImage(qrCode.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH), 0, 0, null);
            qrCode = tmp;
        }

        return qrCode;
    }

    /**
     * 获取渐变颜色
     * 
     * @Title getColorVal
     * @author 吕凯
     * @date 2017年8月10日 下午5:28:39
     * @param matrix
     * @param y
     * @return int
     */

    public int getColorVal(BitMatrix matrix, int y) {
        return getColorVal(matrix, y, 40, 165, 162);
    }

    public int getColorVal(BitMatrix matrix, int y, int r, int g, int b) {
        int num1 = (int) (r - (r - 13.0) / matrix.getHeight() * (y + 1)); // 50~13
        int num2 = (int) (g - (g - 72.0) / matrix.getHeight() * (y + 1)); // 165~72.0
        int num3 = (int) (b - (b - 107.0) / matrix.getHeight() * (y + 1)); // 162~107.0
        Color color = new Color(num1, num2, num3);
        int colorInt = color.getRGB();
        return colorInt;
    }

    public static void main(String[] args) {
        QRCodeDataFillWriter writer = new QRCodeDataFillWriter();
        System.out.println(Color.BLACK.getRGB() + " " + Color.WHITE.getRGB());
        BufferedImage bufferImg = writer.encodeBufImg("http://www.baidu.com/", 600, 600, true);
        // 指定生成图片的保存路径
        File file = new File("D:/Desktop/captcha/imooctt2.png");
        // 生成二维码
        try {
            ImageIO.write(bufferImg, "png", file);
        } catch (IOException e) {
            log.error("", e);
        }
    }

}